﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using MSNPSharp;

namespace MessengerBot
{
    /// <summary>
    /// This class encapsulates a session with a user.
    /// </summary>
    public sealed class Session
    {
        /// <summary>
        /// This is the backing field for the <see cref="Bot"/> property.
        /// </summary>
        private readonly Bot _Bot;

        /// <summary>
        /// This is the backing field for the <see cref="Conversation"/> property.
        /// </summary>
        private readonly Conversation _Conversation;

        /// <summary>
        /// This field holds a collection of system state objects for the registered systems.
        /// </summary>
        private readonly List<Tuple<ISystem, object>> _SystemState = new List<Tuple<ISystem, object>>();

        /// <summary>
        /// Initializes a new instance of the <see cref="Session"/> class.
        /// </summary>
        /// <param name="bot">
        /// The bot that manages this <see cref="Session"/>.
        /// </param>
        /// <param name="conversation">
        /// The <see cref="Conversation"/> object this <see cref="Session"/> will encapsulate.
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="bot"/> is <c>null</c>.</para>
        /// <para>- or -</para>
        /// <para><paramref name="conversation"/> is <c>null</c>.</para>
        /// </exception>
        public Session(Bot bot, Conversation conversation)
        {
            if (bot == null)
                throw new ArgumentNullException("bot");
            if (conversation == null)
                throw new ArgumentNullException("conversation");

            _Bot = bot;
            _Conversation = conversation;

            _Conversation.ContactJoined += ContactJoined;
            _Conversation.ContactLeft += ContactLeft;
            _Conversation.ConversationEnded += ConversationEnded;
            _Conversation.EmoticonDefinitionReceived += EmoticonDefinitionReceived;
            _Conversation.ExceptionOccurred += ExceptionOccurred;
            _Conversation.MSNObjectDataTransferCompleted += MSNObjectDataTransferCompleted;
            _Conversation.NudgeReceived += NudgeReceived;
            _Conversation.RemoteOwnerChanged += RemoteOwnerChanged;
            _Conversation.ServerErrorReceived += ServerErrorReceived;
            _Conversation.SessionClosed += SessionClosed;
            _Conversation.SessionEstablished += SessionEstablished;
            _Conversation.TextMessageReceived += TextMessageReceived;
            _Conversation.UserTyping += UserTyping;
            _Conversation.WinkReceived += WinkReceived;
        }

        /// <summary>
        /// Gets the <see cref="Bot"/> that manages this <see cref="Session"/>.
        /// </summary>
        public Bot Bot
        {
            get
            {
                return _Bot;
            }
        }

        /// <summary>
        /// Gets the <see cref="Conversation"/> that this <see cref="Session"/> encapsulates.
        /// </summary>
        public Conversation Conversation
        {
            get
            {
                return _Conversation;
            }
        }

        /// <summary>
        /// Event, WinkReceived.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="MSNPSharp.WinkEventArgs"/> instance containing the event data.</param>
        private void WinkReceived(object sender, WinkEventArgs e)
        {
        }

        /// <summary>
        /// Event, UserTyping.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="MSNPSharp.ContactEventArgs"/> instance containing the event data.</param>
        private void UserTyping(object sender, ContactEventArgs e)
        {
        }

        /// <summary>
        /// Event, TextMessageReceived.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="MSNPSharp.TextMessageEventArgs"/> instance containing the event data.</param>
        private void TextMessageReceived(object sender, TextMessageEventArgs e)
        {
            foreach (var systemState in _SystemState)
                systemState.Item1.MessageReceived(Bot, this, systemState.Item2, e.Message.Text);

            // TODO: Handle quotes
            string[] parts = e.Message.Text.Split(
                new[]
                {
                    ' '
                }, StringSplitOptions.RemoveEmptyEntries);
            string[] arguments;
            ICommand command = CommandParser.GetCommand(_Bot.Commands, parts, out arguments);
            if (command != null)
            {
                _Conversation.SendTypingMessage();

                Type commandType = command.GetType();
                ICommandBasedSystem ownerSystem = (from system in _Bot.Configuration.Systems.OfType<ICommandBasedSystem>()
                                                   where system.GetCommands().Contains(commandType)
                                                   select system).FirstOrDefault();
                Debug.Assert(ownerSystem != null, "ownerSystem should not be null here");

                try
                {
                    command.Execute(ownerSystem, this, arguments);
                }
                catch (CommandException ex)
                {
                    _Conversation.SendTextMessage(new TextMessage("Error: " + ex.Message));
                    _Conversation.SendTextMessage(new TextMessage("If you need assistance, use the command 'help'"));
                }
                catch (Exception ex)
                {
                    _Conversation.SendTextMessage(new TextMessage("An exception occured :("));
                    _Conversation.SendTextMessage(new TextMessage(ex.GetType().FullName + ": " + ex.Message));
                    _Conversation.SendTextMessage(new TextMessage(ex.StackTrace));
                }
            }
            else
                _Conversation.SendTextMessage(new TextMessage("Unknown command, use the command 'help' for help"));
        }

        /// <summary>
        /// Event, SessionEstablished.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void SessionEstablished(object sender, EventArgs e)
        {
            _SystemState.Clear();
            foreach (ISystem system in Bot.Configuration.Systems)
            {
                object state = system.SessionStarted(Bot, this);
                _SystemState.Add(Tuple.Create(system, state));
            }
        }

        /// <summary>
        /// Event, SessionClosed.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void SessionClosed(object sender, EventArgs e)
        {
            foreach (var systemState in _SystemState)
                systemState.Item1.SessionEnded(Bot, this, systemState.Item2);
            _SystemState.Clear();
        }

        /// <summary>
        /// Event, ServerErrorReceived.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="MSNPSharp.MSNErrorEventArgs"/> instance containing the event data.</param>
        private void ServerErrorReceived(object sender, MSNErrorEventArgs e)
        {
        }

        /// <summary>
        /// Event, RemoteOwnerChanged.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="MSNPSharp.ConversationRemoteOwnerChangedEventArgs"/> instance containing the event data.</param>
        private void RemoteOwnerChanged(object sender, ConversationRemoteOwnerChangedEventArgs e)
        {
        }

        /// <summary>
        /// Event, NudgeReceived.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="MSNPSharp.ContactEventArgs"/> instance containing the event data.</param>
        private void NudgeReceived(object sender, ContactEventArgs e)
        {
        }

        /// <summary>
        /// Event, MSNObjectDataTransferCompleted.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="MSNPSharp.ConversationMSNObjectDataTransferCompletedEventArgs"/> instance containing the event data.</param>
        private void MSNObjectDataTransferCompleted(object sender, ConversationMSNObjectDataTransferCompletedEventArgs e)
        {
        }

        /// <summary>
        /// Event, ExceptionOccurred.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="MSNPSharp.ExceptionEventArgs"/> instance containing the event data.</param>
        private void ExceptionOccurred(object sender, ExceptionEventArgs e)
        {
        }

        /// <summary>
        /// Event, EmoticonDefinitionReceived.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="MSNPSharp.EmoticonDefinitionEventArgs"/> instance containing the event data.</param>
        private void EmoticonDefinitionReceived(object sender, EmoticonDefinitionEventArgs e)
        {
        }

        /// <summary>
        /// Event, ConversationEnded.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="MSNPSharp.ConversationEndEventArgs"/> instance containing the event data.</param>
        private void ConversationEnded(object sender, ConversationEndEventArgs e)
        {
        }

        /// <summary>
        /// Event, ContactLeft.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="MSNPSharp.ContactConversationEventArgs"/> instance containing the event data.</param>
        private void ContactLeft(object sender, ContactConversationEventArgs e)
        {
        }

        /// <summary>
        /// Event, ContactJoined.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="MSNPSharp.ContactConversationEventArgs"/> instance containing the event data.</param>
        private void ContactJoined(object sender, ContactConversationEventArgs e)
        {
        }
    }
}